package com.frank.loadinglibrary.special.element;

import com.frank.loadinglibrary.ResourceTable;
import com.frank.loadinglibrary.base.baseanimations.BaseAnimationController;
import com.frank.loadinglibrary.base.baseelement.BaseElement;
import com.frank.loadinglibrary.base.baseelement.IComponentCallback;
import com.frank.loadinglibrary.utils.DensityUtil;
import com.frank.loadinglibrary.utils.SimpleAnimatorListener;
import com.frank.loadinglibrary.utils.interpolator.*;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author SUQI
 * @date 2021/6/3
 * @description
 **/
public class ElectricFanElement extends BaseElement implements BaseAnimationController.IAnimationController<AnimatorValue> {

    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
    private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
    private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
    private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
    private static final Interpolator FASTOUTLINEARIN_INTERPOLATOR = new FastOutLinearInInterpolator();

    private static final Interpolator[] INTERPOLATORS = new Interpolator[]{LINEAR_INTERPOLATOR,
            DECELERATE_INTERPOLATOR, ACCELERATE_INTERPOLATOR, FASTOUTLINEARIN_INTERPOLATOR, MATERIAL_INTERPOLATOR};

    private static final List<LeafHolder> LEAF_HOLDERS = new ArrayList<>();
    private static final Random RANDOM = new Random();

    public static final int MODE_NORMAL = 0;
    public static final int MODE_LEAF_COUNT = 1;

    private static final String PERCENTAGE_100 = "100%";

    private static final long ANIMATION_DURATION = 7333;

    private static final int LEAF_COUNT = 28;
    private static final int DEGREE_180 = 180;
    private static final int DEGREE_360 = 360;
    private static final int FULL_GROUP_ROTATION = (int) (5.25f * DEGREE_360);

    private static final float DEFAULT_WIDTH = 182.0f;
    private static final float DEFAULT_HEIGHT = 65.0f;
    private static final float DEFAULT_TEXT_SIZE = 11.0f;
    private static final float DEFAULT_STROKE_WIDTH = 2.0f;
    private static final float DEFAULT_STROKE_INTERVAL = .2f;
    private static final float DEFAULT_CENTER_RADIUS = 16.0f;
    private static final float DEFAULT_PROGRESS_CENTER_RADIUS = 11.0f;

    private static final float DEFAULT_LEAF_FLY_DURATION_FACTOR = 0.1f;

    private static final float LEAF_CREATE_DURATION_INTERVAL = 1.0f / LEAF_COUNT;
    private static final float DECELERATE_DURATION_PERCENTAGE = 0.4f;
    private static final float ACCELERATE_DURATION_PERCENTAGE = 0.6f;

    private Paint mPaint;
    private final RectFloat arcBounds = new RectFloat();
    private final RectFloat mCurrentProgressBounds = new RectFloat();

    private float mWidth;
    private float mHeight;

    private float mTextSize;
    private float mStrokeXaxisInset;
    private float mStrokeYaxisInset;
    private float mProgressCenterRadius;

    private float mScale;
    private float mRotation;
    private float mProgress;

    private float mNextLeafCreateThreshold;

    private Color mProgressColor;
    private Color mProgressBgColor;
    private Color mElectricFanBgColor;
    private Color mElectricFanOutlineColor;

    private float mStrokeWidth;
    private float mCenterRadius;

    private int mMode;
    private int mCurrentLeafCount;
    private long mDuration;

    private ResizedPixelMapHolder mLeafHolder;
    private ResizedPixelMapHolder mLoadingHolder;
    private ResizedPixelMapHolder mElectricFanHolder;
    private float valueOne = 1f;


    private ElectricFanElement(IComponentCallback callback) {
        this.callback = callback;
    }

    private void apply(ElectricFanBuilder builder) {
        mMode = MODE_NORMAL;

        mWidth = DensityUtil.dip2px(callback.getContext(), DEFAULT_WIDTH);
        mHeight = DensityUtil.dip2px(callback.getContext(), DEFAULT_HEIGHT);
        mTextSize = DensityUtil.dip2px(callback.getContext(), DEFAULT_TEXT_SIZE);
        mStrokeWidth = DensityUtil.dip2px(callback.getContext(), DEFAULT_STROKE_WIDTH);
        mCenterRadius = DensityUtil.dip2px(callback.getContext(), DEFAULT_CENTER_RADIUS);
        mProgressCenterRadius = DensityUtil.dip2px(callback.getContext(), DEFAULT_PROGRESS_CENTER_RADIUS);

        mProgressColor = builder.mProgressColor;
        mProgressBgColor = builder.mProgressBgColor;
        mElectricFanBgColor = builder.mElectricFanBgColor;
        mElectricFanOutlineColor = builder.mElectricFanOutlineColor;

        mLeafHolder = getPixelMapHolder(callback.getContext(), ResourceTable.Media_ic_leaf);
        mLoadingHolder = getPixelMapHolder(callback.getContext(), ResourceTable.Media_ic_loading);
        mElectricFanHolder = getPixelMapHolder(callback.getContext(), ResourceTable.Media_ic_eletric_fan);

        mDuration = ANIMATION_DURATION;
        setInsets((int) mWidth, (int) mHeight);
    }

    private void setInsets(int width, int height) {
        final float minEdge = (float) Math.min(width, height);
        float insetXs;
        if (mCenterRadius <= 0 || minEdge < 0) {
            insetXs = (float) Math.ceil(mCenterRadius / 2.0f);
        } else {
            insetXs = mCenterRadius;
        }
        mStrokeYaxisInset = (float) Math.ceil(mCenterRadius / 2.0f);
        mStrokeXaxisInset = insetXs;
    }

    @Override
    public ArrayList<AnimatorValue> onCreateAnimators() {
        reset();
        ArrayList<AnimatorValue> list = new ArrayList<>();
        AnimatorValue animatorValue = new AnimatorValue();
        animatorValue.setLoopedCount(Animator.INFINITE);
        animatorValue.setDuration(mDuration);
        animatorValue.setValueUpdateListener((animatorValue1, v) -> {
            computeRender(v);
            callback.reflushComponent();
        });
        animatorValue.setLoopedListener(animator -> {
            reset();
        });
        list.add(animatorValue);
        return list;
    }

    @Override
    public void onAnimationReset() {
        //do nothing because donot need to reset animation params
    }

    @Override
    public BaseAnimationController.IAnimationController<AnimatorValue> initAnimationController() {
        return this;
    }

    @Override
    public void initPaint() {
        super.initPaint();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        int saveCount = canvas.save();
        setRect(arcBounds, 0, 0, mWidth, mHeight);
        arcBounds.shrink(mStrokeXaxisInset, mStrokeYaxisInset);
        setRect(mCurrentProgressBounds, arcBounds.left, arcBounds.bottom - 2 * mCenterRadius,
                arcBounds.right, arcBounds.bottom);
        float scale = Math.min(getWidth() / mWidth, getHeight() / mHeight);
        canvas.scale(scale, scale, getWidth() / 2f, getHeight() / 2f);
        canvas.translate((getWidth() - mWidth) / 2f, (getHeight() - mHeight) / 2f);
        //draw loading drawable
        canvas.drawPixelMapHolderRect(mLoadingHolder,
                new RectFloat((int) arcBounds.getCenter().getPointX() - mLoadingHolder.width / 2,
                        0,
                        (int) arcBounds.getCenter().getPointX() + mLoadingHolder.width / 2,
                        mLoadingHolder.height), mPaint);

        //draw progress background
        float progressInset = mCenterRadius - mProgressCenterRadius;
        RectFloat progressRect = new RectFloat(mCurrentProgressBounds);
        //sub DEFAULT_STROKE_INTERVAL, otherwise will have a interval between progress background and progress outline
        progressRect.shrink(progressInset - DEFAULT_STROKE_INTERVAL, progressInset - DEFAULT_STROKE_INTERVAL);
        mPaint.setColor(mProgressBgColor);
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        canvas.drawRoundRect(progressRect, mProgressCenterRadius, mProgressCenterRadius, mPaint);

        //draw progress
        mPaint.setColor(mProgressColor);
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        canvas.drawPath(createProgressPath(mProgress, mProgressCenterRadius, progressRect), mPaint);

        //draw leaves
        for (int i = 0; i < LEAF_HOLDERS.size(); i++) {
            int leafSaveCount = canvas.save();
            LeafHolder leafHolder = LEAF_HOLDERS.get(i);
            Rect leafBounds = leafHolder.mLeafRect;

            canvas.rotate(leafHolder.mLeafRotation, leafBounds.getCenterX(), leafBounds.getCenterY());
            canvas.drawPixelMapHolderRect(new PixelMapHolder(mLeafHolder.getPixelMap()),
                    new RectFloat(leafBounds.left, leafBounds.top, leafBounds.right, leafBounds.bottom), mPaint);
            canvas.restoreToCount(leafSaveCount);
        }

        //draw progress background outline,
        //after drawing the leaves and then draw the outline of the progress background can
        //prevent the leaves from flying to the outside
        RectFloat progressOutlineRect = new RectFloat(mCurrentProgressBounds);
        float progressOutlineStrokeInset = (mCenterRadius - mProgressCenterRadius) / 2.0f;
        progressOutlineRect.shrink(progressOutlineStrokeInset, progressOutlineStrokeInset);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setColor(mProgressBgColor);
        mPaint.setStrokeWidth(mCenterRadius - mProgressCenterRadius);
        canvas.drawRoundRect(progressOutlineRect, mCenterRadius, mCenterRadius, mPaint);

        //draw electric fan outline
        float electricFanCenterX = arcBounds.right - mCenterRadius;
        float electricFanCenterY = arcBounds.bottom - mCenterRadius;

        mPaint.setColor(mElectricFanOutlineColor);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setStrokeWidth(mStrokeWidth);
        canvas.drawCircle(arcBounds.right - mCenterRadius, arcBounds.bottom - mCenterRadius,
                mCenterRadius - mStrokeWidth / 2.0f, mPaint);

        //draw electric background
        mPaint.setColor(mElectricFanBgColor);
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        canvas.drawCircle(arcBounds.right - mCenterRadius, arcBounds.bottom - mCenterRadius,
                mCenterRadius - mStrokeWidth + DEFAULT_STROKE_INTERVAL, mPaint);

        //draw electric fan
        int rotateSaveCount = canvas.save();
        canvas.rotate(mRotation, electricFanCenterX, electricFanCenterY);
        canvas.drawPixelMapHolderRect(new PixelMapHolder(mElectricFanHolder.getPixelMap()),
                new RectFloat((int) (electricFanCenterX - mElectricFanHolder.width / 2 * mScale),
                        (int) (electricFanCenterY - mElectricFanHolder.height / 2 * mScale),
                        (int) (electricFanCenterX + mElectricFanHolder.width / 2 * mScale),
                        (int) (electricFanCenterY + mElectricFanHolder.height / 2 * mScale)), mPaint);
        canvas.restoreToCount(rotateSaveCount);

        //draw 100% text
        if (mScale < valueOne) {
            mPaint.setTextSize((int) (mTextSize * (1 - mScale)));
            mPaint.setColor(mElectricFanOutlineColor);
            Rect textRect = mPaint.getTextBounds(PERCENTAGE_100);
            canvas.drawText(mPaint, PERCENTAGE_100, electricFanCenterX - textRect.getWidth() / 2.0f,
                    electricFanCenterY + textRect.getHeight() / 2.0f);
        }

        canvas.restoreToCount(saveCount);
    }

    private void reset() {
        mScale = 1.0f;
        mCurrentLeafCount = 0;
        mNextLeafCreateThreshold = 0.0f;
        LEAF_HOLDERS.clear();
    }

    private void computeRender(float renderProgress) {
        if (renderProgress < DECELERATE_DURATION_PERCENTAGE) {
            mProgress = DECELERATE_INTERPOLATOR.getInterpolation(renderProgress / DECELERATE_DURATION_PERCENTAGE) * DECELERATE_DURATION_PERCENTAGE;
        } else {
            mProgress = ACCELERATE_INTERPOLATOR.getInterpolation((renderProgress - DECELERATE_DURATION_PERCENTAGE) / ACCELERATE_DURATION_PERCENTAGE) * ACCELERATE_DURATION_PERCENTAGE + DECELERATE_DURATION_PERCENTAGE;
        }
    }

    public void setRect(RectFloat rect, float left, float top, float right, float bottom) {
        rect.left = left;
        rect.top = top;
        rect.right = right;
        rect.bottom = bottom;
    }

    private Path createProgressPath(float progress, float circleRadius, RectFloat progressRect) {
        RectFloat arcProgressRect = new RectFloat(progressRect.left, progressRect.top, progressRect.left + circleRadius * 2, progressRect.bottom);
        RectFloat rectProgressRect = null;

        float progressWidth = progress * progressRect.getWidth();
        float progressModeWidth = mMode == MODE_LEAF_COUNT ?
                (float) mCurrentLeafCount / (float) LEAF_COUNT * progressRect.getWidth() : progress * progressRect.getWidth();

        float swipeAngle = DEGREE_180;
        //the left half circle of the progressbar
        if (progressModeWidth < circleRadius) {
            swipeAngle = progressModeWidth / circleRadius * DEGREE_180;
        }

        //the center rect of the progressbar
        if (progressModeWidth < progressRect.getWidth() - circleRadius && progressModeWidth >= circleRadius) {
            rectProgressRect = new RectFloat(progressRect.left + circleRadius, progressRect.top, progressRect.left + progressModeWidth, progressRect.bottom);
        }

        //the right half circle of the progressbar
        if (progressWidth >= progressRect.getWidth() - circleRadius) {
            rectProgressRect = new RectFloat(progressRect.left + circleRadius, progressRect.top, progressRect.right - circleRadius, progressRect.bottom);
            mScale = (progressRect.getWidth() - progressWidth) / circleRadius;
        }

        //the left of the right half circle
        if (progressWidth < progressRect.getWidth() - circleRadius) {
            mRotation = (progressWidth / (progressRect.getWidth() - circleRadius)) * FULL_GROUP_ROTATION % DEGREE_360;

            RectFloat leafRect = new RectFloat(progressRect.left + progressWidth, progressRect.top, progressRect.right - circleRadius, progressRect.bottom);
            addLeaf(progress, leafRect);
        }

        Path path = new Path();
        path.addArc(arcProgressRect, DEGREE_180 - swipeAngle / 2, swipeAngle);

        if (rectProgressRect != null) {
            path.addRect(rectProgressRect, Path.Direction.CLOCK_WISE);
        }

        return path;
    }

    private void addLeaf(float progress, RectFloat leafFlyRect) {
        if (progress < mNextLeafCreateThreshold) {
            return;
        }
        mNextLeafCreateThreshold += LEAF_CREATE_DURATION_INTERVAL;

        LeafHolder leafHolder = new LeafHolder();
        LEAF_HOLDERS.add(leafHolder);
        AnimatorGroup leafAnimator = getAnimator(leafHolder, leafFlyRect, progress);
        leafAnimator.setStateChangedListener(new AnimEndListener(leafHolder));
        leafAnimator.start();
    }

    private AnimatorGroup getAnimator(LeafHolder target, RectFloat leafFlyRect, float progress) {
        AnimatorValue bezierValueAnimator = getBezierValueAnimator(target, leafFlyRect, progress);

        AnimatorGroup finalSet = new AnimatorGroup();
        finalSet.runSerially(bezierValueAnimator);
        return finalSet;
    }

    private AnimatorValue getBezierValueAnimator(LeafHolder target, RectFloat leafFlyRect, float progress) {
        BezierEvaluator evaluator = new BezierEvaluator(getPoint1(leafFlyRect), getPoint2(leafFlyRect));

        int leafFlyStartY = (int) (mCurrentProgressBounds.bottom - mLeafHolder.height);
        int leafFlyRange = (int) (mCurrentProgressBounds.getHeight() - mLeafHolder.height);

        int startPointY = leafFlyStartY - RANDOM.nextInt(leafFlyRange);
        int endPointY = leafFlyStartY - RANDOM.nextInt(leafFlyRange);

        AnimatorValue animator = new AnimatorValue();
        animator.setValueUpdateListener(new BezierListener(target, INTERPOLATORS[RANDOM.nextInt(INTERPOLATORS.length)], evaluator,
                new Point((int) (leafFlyRect.right - mLeafHolder.width), startPointY),
                new Point(leafFlyRect.left, endPointY)));

        animator.setDuration((long) ((RANDOM.nextInt(300) + mDuration * DEFAULT_LEAF_FLY_DURATION_FACTOR) * (1.0f - progress)));

        return animator;
    }

    /**
     * get the pointF which belong to the right half side
     * @param leafFlyRect
     * @return
     */
    private Point getPoint1(RectFloat leafFlyRect) {
        Point point = new Point();
        point.position[0] = leafFlyRect.right - RANDOM.nextInt((int) (leafFlyRect.getWidth() / 2 + 1));
        point.position[1] = (int) (leafFlyRect.bottom - RANDOM.nextInt((int) leafFlyRect.getHeight()));
        return point;
    }

    /**
     * get the pointF which belong to the left half side
     * @param leafFlyRect
     * @return
     */
    private Point getPoint2(RectFloat leafFlyRect) {
        Point point = new Point();
        point.position[0] = leafFlyRect.left + RANDOM.nextInt((int) (leafFlyRect.getWidth() / 2 + 1));
        point.position[1] = (int) (leafFlyRect.bottom - RANDOM.nextInt((int) leafFlyRect.getHeight()));
        return point;
    }

    private ResizedPixelMapHolder getPixelMapHolder(Context context, int resourceId) {
        ResizedPixelMapHolder result = null;
        try {
            ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions();
            ImageSource imageSource = ImageSource.create(context.getResourceManager().getResource(resourceId), srcOpts);
            ImageSource.DecodingOptions decodingOpts = new ImageSource.DecodingOptions();
            PixelMap pixelMap = imageSource.createPixelmap(decodingOpts);
            result = new ResizedPixelMapHolder(context, pixelMap);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotExistException e) {
            e.printStackTrace();
        }
        return result;
    }

    private class LeafHolder {
        public Rect mLeafRect = new Rect();
        public float mLeafRotation = 0.0f;

        public float mMaxRotation = RANDOM.nextInt(120);
    }

    private class ResizedPixelMapHolder extends PixelMapHolder {
        public final float width;
        public final float height;

        public ResizedPixelMapHolder(Context context, PixelMap pixelMap) {
            super(pixelMap);
            width = DensityUtil.xldpiPx2IntrinsicPx(context, pixelMap.getImageInfo().size.width);
            height = DensityUtil.xldpiPx2IntrinsicPx(context, pixelMap.getImageInfo().size.height);
        }
    }

    private class BezierEvaluator {

        private Point point1;
        private Point point2;

        public BezierEvaluator(Point point1, Point point2) {
            this.point1 = point1;
            this.point2 = point2;
        }

        //Third-order Bezier curve formula: B(t) = point0 * (1-t)^3 + 3 * point1 * t * (1-t)^2 + 3 * point2 * t^2 * (1-t) + point3 * t^3
        public Point evaluate(float fraction, Point point0, Point point3) {

            float t = fraction;
            float tLeft = 1.0f - t;

            float x = (float) (point0.getPointX() * Math.pow(tLeft, 3) + 3 * point1.getPointX() * t * Math.pow(tLeft, 2) + 3 * point2.getPointX() * Math.pow(t, 2) * tLeft + point3.getPointX() * Math.pow(t, 3));
            float y = (float) (point0.getPointY() * Math.pow(tLeft, 3) + 3 * point1.getPointY() * t * Math.pow(tLeft, 2) + 3 * point2.getPointY() * Math.pow(t, 2) * tLeft + point3.getPointY() * Math.pow(t, 3));

            return new Point(x, y);
        }
    }

    private class BezierListener implements AnimatorValue.ValueUpdateListener {

        private LeafHolder target;
        private Interpolator interpolator;
        private BezierEvaluator evaluator;
        private Point p0;
        private Point p3;

        public BezierListener(LeafHolder target, Interpolator interpolator, BezierEvaluator evaluator, Point p0, Point p3) {
            this.target = target;
            this.interpolator = interpolator;
            this.evaluator = evaluator;
            this.p0 = p0;
            this.p3 = p3;
        }

        @Override
        public void onUpdate(AnimatorValue animation, float animatorValue) {
            float newValue = interpolator.getInterpolation(animatorValue);
            Point point = evaluator.evaluate(newValue, p0, p3);
            target.mLeafRect.set((int) point.getPointX(), (int) point.getPointY(),
                    (int) (point.getPointX() + mLeafHolder.width), (int) (point.getPointY() + mLeafHolder.height));
            target.mLeafRotation = target.mMaxRotation * newValue;
        }
    }

    private class AnimEndListener extends SimpleAnimatorListener {
        private LeafHolder target;

        public AnimEndListener(LeafHolder target) {
            this.target = target;
        }

        @Override
        public void onEnd(Animator animation) {
            LEAF_HOLDERS.remove(target);
            mCurrentLeafCount++;
        }
    }

    public static class ElectricFanBuilder {
        private IComponentCallback iComponentCallback;

        private Color mProgressColor = new Color(0xfffca72e);
        private Color mProgressBgColor = new Color(0xfffcd49f);
        private Color mElectricFanBgColor = new Color(0xfffccc59);
        private Color mElectricFanOutlineColor = Color.WHITE;

        public ElectricFanBuilder(IComponentCallback iComponentCallback) {
            this.iComponentCallback = iComponentCallback;
        }

        public ElectricFanBuilder setProgressColor(Color mProgressColor) {
            this.mProgressColor = mProgressColor;
            return this;
        }

        public ElectricFanBuilder setProgressBgColor(Color mProgressBgColor) {
            this.mProgressBgColor = mProgressBgColor;
            return this;
        }

        public ElectricFanBuilder setElectricFanBgColor(Color mElectricFanBgColor) {
            this.mElectricFanBgColor = mElectricFanBgColor;
            return this;
        }

        public ElectricFanBuilder setElectricFanOutlineColor(Color mElectricFanOutlineColor) {
            this.mElectricFanOutlineColor = mElectricFanOutlineColor;
            return this;
        }

        public ElectricFanElement build() {
            ElectricFanElement electricFanElement = new ElectricFanElement(iComponentCallback);
            electricFanElement.apply(this);
            return electricFanElement;
        }
    }
}
